package com.szjz.elasticsearch.eshelper.base;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.szjz.elasticsearch.eshelper.animation.ESFieldIgnore;
import com.szjz.elasticsearch.eshelper.animation.ESTable;
import com.szjz.elasticsearch.eshelper.page.Page;
import com.szjz.elasticsearch.eshelper.property.ESRestClientProperties;
import com.szjz.elasticsearch.eshelper.util.DateUtils;
import com.szjz.elasticsearch.eshelper.wrapper.Condition;
import com.szjz.elasticsearch.eshelper.wrapper.ESQueryWrapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.*;

/**
 * @author shibo
 * @desc ES 小助手
 * @date 2020/6/3 周三
 */
@Slf4j
@Component
public class BaseHelper<T extends com.szjz.elasticsearch.eshelper.base.BaseESEntity> {


    /**
     * 每次获取的最大数据量
     */
    protected static final Integer SIZE = 100000;

    /**
     * 高级别的REST客户端，基于低级别的REST客户端，增加了编组请求JSON串、解析响应JSON串等相关api
     * 使用的版本需要保持和ES服务端的版本一致，否则会有版本问题
     */
    @Autowired
    protected RestHighLevelClient restHighLevelClient;
    @Autowired
    private ESRestClientProperties esRestClientProperties;


    /**
     * 索引库名称分隔符
     */
    protected final String INDEX_NAME_SPLIT = "-";

    protected XContentBuilder getJsonBuilder(T t) throws IOException {
        Class<T> entityClass = getEntityClass();
        List<Field> list = new ArrayList<>();
        List<Field> superFields = Arrays.asList(entityClass.getSuperclass().getDeclaredFields());
        List<Field> fields = Arrays.asList(entityClass.getDeclaredFields());
        list.addAll(fields);
        list.addAll(superFields);
        List<Field> notIgnoreFields = list.stream().filter(field -> field.getAnnotation(ESFieldIgnore.class) == null && !"serialVersionUID".equals(field.getName())).collect(Collectors.toList());
        XContentBuilder xContentBuilder = jsonBuilder().startObject();
        for (Field declaredField : notIgnoreFields) {
            declaredField.setAccessible(true);
            Object value = null;
            String name = null;
            try {
                name = declaredField.getName();
                Class<?> type = declaredField.getType();
                value = declaredField.get(t);

                if (type == Date.class && value != null) {
                    Date date = DateUtils.CSTToDate(value.toString());
                    assert date != null;
                    value = date.getTime();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if (value != null) {
                xContentBuilder.field(name, value);
            }
        }
        xContentBuilder.endObject();
        return xContentBuilder;
    }


    /**
     * 将实体类转换成map集合
     */
    protected Map<String, Object> getSourceMap(T t) {
        Class<T> entityClass = getEntityClass();
        List<Field> list = new ArrayList<>();
        List<Field> superFields = Arrays.asList(entityClass.getSuperclass().getDeclaredFields());
        List<Field> fields = Arrays.asList(entityClass.getDeclaredFields());
        list.addAll(fields);
        list.addAll(superFields);
        List<Field> notIgnoreFields = list.stream().filter(field -> field.getAnnotation(ESFieldIgnore.class) == null && !"serialVersionUID".equals(field.getName())).collect(Collectors.toList());
        Map<String, Object> fieldMap = new HashMap<>();
        for (Field declaredField : notIgnoreFields) {
            declaredField.setAccessible(true);
            Object value = null;
            String name = null;
            try {
                name = declaredField.getName();
                Class<?> type = declaredField.getType();
                value = declaredField.get(t);
                if (type == Date.class && value != null) {
                    Date date = DateUtils.CSTToDate(value.toString());
                    assert date != null;
                    value = date.getTime();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if (value != null) {
                fieldMap.put(name, value);
            }
        }
        return fieldMap;
    }


    /**
     * 获取类中属性的有效值
     */
    protected Map<String, Object> getValidValue(T t) {
        Class<T> entityClass = getEntityClass();
        List<Field> list = new ArrayList<>();
        List<Field> superFields = Arrays.asList(entityClass.getSuperclass().getDeclaredFields());
        List<Field> fields = Arrays.asList(entityClass.getDeclaredFields());
        list.addAll(fields);
        list.addAll(superFields);
        List<Field> notIgnoreFields = list.stream().filter(field -> field.getAnnotation(ESFieldIgnore.class) == null && !"serialVersionUID".equals(field.getName())).collect(Collectors.toList());
        Map<String, Object> fieldMap = new HashMap<>();
        for (Field declaredField : notIgnoreFields) {
            declaredField.setAccessible(true);
            Object value = null;
            String name = null;
            try {
                name = declaredField.getName();
                Class<?> type = declaredField.getType();
                value = declaredField.get(t);
                if (type == Date.class && value != null) {
                    Date date = DateUtils.CSTToDate(value.toString());
                    assert date != null;
                    value = date.getTime();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if (value != null) {
                fieldMap.put(name, value);
            }
        }
        log.debug("【获取类中属性的有效值】{}", fieldMap);
        return fieldMap;
    }


    /**
     * 获取实体类下划线命名
     *
     * @return
     */
    protected String getIndexName() {
        Class<T> entityClass = getEntityClass();
        ESTable esTable = entityClass.getAnnotation(ESTable.class);
        String s = esTable.indexName();
        log.debug("【搜索索引库】{}", s);
        return esTable.indexName();
    }

    /**
     * 获取实体类下划线命名
     *
     * @return
     */
    protected String getIndexName(Date date) {
        String YMD = DateUtils.format(date, DateUtils.YYYY_MM_DD);
        String s = getIndexName() + INDEX_NAME_SPLIT + YMD;
        log.debug("【搜索索引库-按日分库】{}", s);
        return s;
    }

    /**
     * 根据时间数组获取索引库集合
     *
     * @return
     */
    protected List<String> getIndexNames(List<Date> dates) {
        List<String> list = new ArrayList<>();
        for (Date date : dates) {
            String YMD = DateUtils.format(date, DateUtils.YYYY_MM_DD);
            list.add(getIndexName() + INDEX_NAME_SPLIT + YMD);
        }
        return list;
    }

    /**
     * 获取搜索请求--按日分库
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return
     */
    protected SearchRequest getSearchRequestAcDay(Date startDate, Date endDate) {
        return new SearchRequest(getIndexNamesAcDay(startDate, endDate), new SearchSourceBuilder());
    }

    /**
     * 获取搜索请求--按日分库
     * 兼容老方法
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return
     */
    @Deprecated
    protected SearchRequest getSearchRequest(Date startDate, Date endDate) {
        return new SearchRequest(getIndexNamesAcDay(startDate, endDate), new SearchSourceBuilder());
    }

    /**
     * 获取按天分库的索引库名称
     */
    protected String[] getIndexNamesAcDay(Date startDate, Date endDate) {
        Integer historyDay = esRestClientProperties.getSearchBeforeDay();
        Date today = new Date();
        // 如果限制了历史数据的查询天数
        if (historyDay != null && historyDay > 0) {
            Calendar CALENDAR = Calendar.getInstance();
            CALENDAR.add(Calendar.DAY_OF_MONTH, -historyDay + 1);
            Date historyDate = CALENDAR.getTime();
            // 开始时间 >= 今天 只能查询今天的数据
            if (startDate.compareTo(today) >= 0) {
                startDate = today;
                endDate = today;
            } else {
                // 开始时间 < 今天 且 >= 历史时间
                if (startDate.compareTo(historyDate) >= 0) {
                    if (endDate.compareTo(today) >= 0) {
                        endDate = today;
                    }
                }
                // 开始时间 < 历史时间
                else {
                    startDate = historyDate;
                    if (endDate.compareTo(historyDate) <= 0) {
                        endDate = historyDate;
                    } else {
                        if (endDate.compareTo(today) >= 0) {
                            endDate = today;
                        }
                    }
                }
            }
            log.info("【搜索索引库-按日分库】historyDay:{} startDate:{} endDate:{}", historyDay, startDate, endDate);
        }
        // 没有查询时间的限定
        else {
            // 开始时间 >= 今天 只能查询今天的数据
            if (startDate.compareTo(today) >= 0) {
                startDate = today;
                endDate = today;
            }
            // 开始时间 < 今天
            else {
                // 结束时间 >= 今天 用今天
                if (endDate.compareTo(today) >= 0) {
                    endDate = today;
                }
            }
            log.info("【搜索索引库-按日分库】historyDay:{} startDate:{} endDate:{}", historyDay, startDate, endDate);
        }

        Date start = DateUtils.getFirstMomentOfDay(startDate);
        Date end = DateUtils.getLastMomentOfDay(endDate);
        long disDay = DateUtils.getDisDay(start, end);
        List<Date> dates = new ArrayList<>();
        assert start != null;
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(start);
        dates.add(start);
        for (int i = 0; i < disDay; i++) {
            calendar.add(Calendar.DAY_OF_MONTH, 1);
            Date date = calendar.getTime();
            dates.add(date);
            calendar.setTime(date);
        }
        List<String> indexNames = getIndexNames(dates);
        log.info("【搜索索引库-按天分库】size: {} 开始时间：{} 结束时间：{} 相差天数：{} 索引库集合：{}", indexNames.size(), start, end, disDay, indexNames);
        return indexNames.toArray(new String[0]);
    }

    /**
     * 获取实体类下划线命名--按月分库
     *
     * @return
     */
    protected String getIndexNameAcMonth(Date date) {
        String YM = DateUtils.format(date, DateUtils.YYYY_MM);
        String s = getIndexName() + INDEX_NAME_SPLIT + YM;
        log.debug("【搜索索引库-按月分库】{}", s);
        return s;
    }

    /**
     * 获取实体类下划线命名--按月分库
     *
     * @return
     */
    protected String getIndexNameAcMonth(String yearMonth) {
        return getIndexName() + INDEX_NAME_SPLIT + yearMonth;
    }

    /**
     * 获取搜索请求--按月分库
     *
     * @param startYMD 开始时间 格式：yyyy-MM-dd 索引库名称结尾日期格式
     * @param endYMD   结束时间 格式：yyyy-MM-dd 索引库名称结尾日期格式
     * @return
     */
    protected SearchRequest getSearchRequestAcMonth(String startYMD, String endYMD) {
        Date startDate = DateUtils.parse(startYMD + " 00:00:00", DateUtils.PATTERN_YYMMDDHHMMSS);
        Date endDate = DateUtils.parse(endYMD + " 00:00:00", DateUtils.PATTERN_YYMMDDHHMMSS);
        return getSearchRequestAcMonth(startDate, endDate);
    }

    /**
     * 获取搜索请求--按月分库
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return
     */
    protected SearchRequest getSearchRequestAcMonth(Date startDate, Date endDate) {
        String[] indexNames = getIndexNamesAcMonth(startDate, endDate);
        return new SearchRequest(indexNames, new SearchSourceBuilder());
    }

    /**
     * 根据时间段获取索引库名称数组
     *
     * @param startYM 开始时间
     * @param endYM   结束时间
     */
    protected String[] getIndexNamesAcMonth(String startYM, String endYM) {
        Date startDate = DateUtils.parse(startYM + "-01 00:00:00", DateUtils.PATTERN_YYMMDDHHMMSS);
        Date endDate = DateUtils.parse(endYM + "-01 00:00:00", DateUtils.PATTERN_YYMMDDHHMMSS);
        return getIndexNamesAcMonth(startDate, endDate);
    }

    /**
     * 根据时间段获取索引库名称数组
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     */
    protected String[] getIndexNamesAcMonth(Date startDate, Date endDate) {
        // 开始
        Calendar start = Calendar.getInstance();
        start.setTime(startDate);
        int startYear = start.get(Calendar.YEAR);
        int startMonth = start.get(Calendar.MONTH) + 1;

        // 结束
        Calendar end = Calendar.getInstance();
        end.setTime(endDate);
        int endYear = end.get(Calendar.YEAR);
        int endMonth = end.get(Calendar.MONTH) + 1;

        // 日期差
        DateUtils.DayCompare dayCompare = DateUtils.dateCompare(startDate, endDate);
        int diffYear = dayCompare.getDiffYear();
        int diffMonth = dayCompare.getDiffMonth();

        List<String> indexNames = new ArrayList<>();
        // 跨年 开始年份 < 结束年份
        if (diffYear > 0) {
            // 计算开始时间离12月份的月数
            int num = 12 - startMonth;
            for (int month = startMonth; month <= num + startMonth; month++) {
                String yearMonth = startYear + "_" + String.format("%02d", month);
                String indexNameAcMonth = getIndexNameAcMonth(yearMonth);
                indexNames.add(indexNameAcMonth);
            }
            // 计算结束时间离1月份的月数
            for (int month = 1; month <= endMonth; month++) {
                String yearMonth = endYear + "_" + String.format("%02d", month);
                String indexNameAcMonth = getIndexNameAcMonth(yearMonth);
                indexNames.add(indexNameAcMonth);
            }
            log.info("【搜索索引库-按月分库】跨年 开始年份 < 结束年份 start:{} end:{}", startYear + "-" + startMonth, endYear + "-" + endMonth);
        }
        // 跨年 开始年份 > 结束年份 以结束年分的那个月为准
        else if (diffYear < 0) {
            String indexNameAcMonth = getIndexNameAcMonth(endDate);
            indexNames.add(indexNameAcMonth);
            log.info("【搜索索引库-按月分库】跨年 开始年份 > 结束年份 start:{} end:{}", startYear + "-" + startMonth, endYear + "-" + endMonth);
        }
        // 同年 开始年份 = 结束年份
        else {
            // 同月
            if (diffMonth == 0) {
                String indexNameAcMonth = getIndexNameAcMonth(startDate);
                indexNames.add(indexNameAcMonth);
                log.info("【搜索索引库-按月分库】同年 同月 start:{} end:{}", startYear + "-" + startMonth, endYear + "-" + endMonth);
            }
            // 开始月 < 结束月
            else if (diffMonth > 0) {
                int num = endMonth - startMonth;
                for (int month = startMonth; month <= startMonth + num; month++) {
                    String yearMonth = startYear + "_" + String.format("%02d", month);
                    indexNames.add(getIndexNameAcMonth(yearMonth));
                }
                log.info("【搜索索引库-按月分库】同年 开始月 < 结束月 start:{} end:{}", startYear + "-" + startMonth, endYear + "-" + endMonth);
            }
            // 开始月 > 结束月 按照结束月份对应的时间查询
            else {
                String indexNameAcMonth = getIndexNameAcMonth(endDate);
                indexNames.add(indexNameAcMonth);
                log.info("【搜索索引库-按月分库】同年 开始月 > 结束月 按照结束月份对应的时间查询 start:{} end:{}", startYear + "-" + startMonth, endYear + "-" + endMonth);
            }
        }
        log.info("【搜索索引库-按月分库】搜索索引库 size: {} 开始时间：{} 结束时间：{} 相差月数：{} 索引库集合：{}", indexNames.size(), startYear + "-" + startMonth, endYear + "-" + endMonth, diffMonth, indexNames);
        return indexNames.toArray(new String[0]);
    }


    /**
     * 获取搜索请求
     *
     * @param startDateStr 开始时间
     * @param endDateStr   结束时间
     * @return
     */
    protected SearchRequest getSearchRequestAcDay(String startDateStr, String endDateStr) {
        Date startDate = DateUtils.parse(startDateStr, DateUtils.PATTERN_YYMMDDHHMMSS);
        Date endDate = DateUtils.parse(endDateStr, DateUtils.PATTERN_YYMMDDHHMMSS);
        return getSearchRequestAcDay(startDate, endDate);
    }

    /**
     * 按天分库-获取搜索请求
     * 兼容老方法
     *
     * @param startDateStr 开始时间
     * @param endDateStr   结束时间
     * @return
     */
    @Deprecated
    protected SearchRequest getSearchRequest(String startDateStr, String endDateStr) {
        Date startDate = DateUtils.parse(startDateStr, DateUtils.PATTERN_YYMMDDHHMMSS);
        Date endDate = DateUtils.parse(endDateStr, DateUtils.PATTERN_YYMMDDHHMMSS);
        return getSearchRequestAcDay(startDate, endDate);
    }


    /**
     * 获取类的实际类型
     *
     * @return 类名全称
     */
    protected Class<T> getEntityClass() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return (Class<T>) parameterizedType.getActualTypeArguments()[0];
        }
        return null;
    }

    /**
     * 初始化 条件建造者
     *
     * @param current
     * @param size
     * @return
     */
    protected SearchSourceBuilder initSearchSourceBuilder(SearchSourceBuilder searchSourceBuilder, Integer current, Integer size) {
        // 分页信息设置
        current = current == null || current <= 0 ? 0 : current - 1;
        size = size == null ? 10 : size;
        searchSourceBuilder.from(current * size);
        searchSourceBuilder.size(size);
        searchSourceBuilder.trackTotalHits(true);
        return searchSourceBuilder;
    }

    /**
     * 初始化 条件建造者
     * 兼容老版本
     *
     * @param current
     * @param size
     * @return
     */
    @Deprecated
    protected SearchSourceBuilder initSearchSourceBuilder(Integer current, Integer size) {
        // 构建查询语句
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 分页信息设置
        current = current == null || current <= 0 ? 0 : current - 1;
        size = size == null ? 10 : size;
        sourceBuilder.from(current * size);
        sourceBuilder.size(size);
        // 设置查询超时时间 1m
        sourceBuilder.timeout(TimeValue.timeValueMinutes(1L));
        sourceBuilder.trackTotalHits(true);
        return sourceBuilder;
    }


    /**
     * 构建分页
     *
     * @param searchRequest
     * @param sourceBuilder
     * @param boolQueryBuilder
     * @param current
     * @param size
     * @return
     */
    @SneakyThrows
    protected Page<T> pageBuilder(SearchRequest searchRequest,
                                  SearchSourceBuilder sourceBuilder,
                                  BoolQueryBuilder boolQueryBuilder,
                                  Integer current,
                                  Integer size) {
        // 构建 json 语句
        sourceBuilder.query(boolQueryBuilder);
        searchRequest.source(sourceBuilder);

        // 处理响应的结果
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits responseHits = response.getHits();
        long total = responseHits.getTotalHits().value;
        SearchHit[] hits = responseHits.getHits();
        List<T> res = new ArrayList<>(hits.length);
        for (SearchHit hit : hits) {
            res.add(JSON.parseObject(hit.getSourceAsString(), getEntityClass()));
        }
        if (esRestClientProperties.getDebug()) {
            log.info("【构建分页】DSL: {}", sourceBuilder.toString());
        }
        // 组装分页
        return Page.buildPage(res, current, size, total);
    }

    /**
     * 构建搜索资源
     */
    protected SearchSourceBuilder getSearchSourceBuilder(List<Condition> conditions, Integer current, Integer size) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 构建分页查询
        if (current != -1 && size != -1) {
            searchSourceBuilder = initSearchSourceBuilder(searchSourceBuilder, current, size);
        }
        // 设置查询超时时间 1m
        searchSourceBuilder.timeout(TimeValue.timeValueMinutes(1L));
        // 构建基础查询条件
        BoolQueryBuilder boolQueryBuilder = getBaseBoolQueryBuilder(conditions);
        // 组装或语句
        getOrBoolQueryBuilder(boolQueryBuilder, conditions);
        // 排序、选择查询资源
        SearchSourceBuilder finalSourceBuilder = searchSourceBuilder;
        Map<String, Condition> map = new HashMap<>();
        conditions.forEach(condition -> {
            switch (condition.getCondition()) {
                case Condition.ORDER_BY_ASC:
                    // 升序
                    log.debug("【condition】{} column: {}, value: {}", Condition.ORDER_BY_ASC, condition.getColumn(), condition.getValue());
                    finalSourceBuilder.sort(condition.getColumn(), SortOrder.ASC);
                    break;
                case Condition.ORDER_BY_DESC:
                    // 降序
                    log.debug("【condition】{} column: {}, value: {}", Condition.ORDER_BY_DESC, condition.getColumn(), condition.getValue());
                    finalSourceBuilder.sort(condition.getColumn(), SortOrder.DESC);
                    break;
                case Condition.INCLUDE_FIELDS:
                    // 包含
                    log.debug("【condition】{} include columns: {}, value: {}", Condition.INCLUDE_FIELDS, condition.getIncludeColumns(), condition.getValue());
                    finalSourceBuilder.fetchSource(condition.getIncludeColumns().toArray(new String[0]), null);
                    map.put("include", condition);
                    break;
                case Condition.EXCLUDE_FIELDS:
                    // 去除
                    log.debug("【condition】{} exclude columns: {}, value: {}", Condition.EXCLUDE_FIELDS, condition.getIncludeColumns(), condition.getValue());
                    finalSourceBuilder.fetchSource(null, condition.getExcludeColumns().toArray(new String[0]));
                    map.put("exclude", condition);
                    break;
                default:
                    break;
            }
        });
        if (map.get("include") != null && map.get("exclude") != null) {
            finalSourceBuilder.fetchSource(map.get("include").getIncludeColumns().toArray(new String[0]), map.get("exclude").getExcludeColumns().toArray(new String[0]));
        }
        searchSourceBuilder.query(boolQueryBuilder);
        return searchSourceBuilder;
    }

    /**
     * 组装或语句
     */
    private BoolQueryBuilder getOrBoolQueryBuilder(BoolQueryBuilder boolQueryBuilder, List<Condition> conditions) {
        List<Condition> orList = conditions.stream()
                .filter(
                        condition -> condition.getCondition().equals(Condition.OR) ||
                                condition.getCondition().equals(Condition.OR_LIKE_LEFT) ||
                                condition.getCondition().equals(Condition.OR_LIKE_RIGHT) ||
                                condition.getCondition().equals(Condition.OR_LIKE)
                )
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(orList)) {
            return boolQueryBuilder;
        }
        BoolQueryBuilder boolQuery = boolQuery();
        // 精准匹配
        orList.stream().filter(condition -> condition.getCondition().equals(Condition.OR))
                .forEach(condition -> {
                    log.debug("【condition】{} exclude columns: {}, value: {}", Condition.OR, condition.getIncludeColumns(), condition.getValue());
                    boolQuery.should(termQuery(condition.getColumn(), condition.getValue()));
                });
        // 左模糊匹配
        orList.stream().filter(condition -> condition.getCondition().equals(Condition.OR_LIKE_LEFT))
                .forEach(condition -> {
                    log.debug("【condition】{} exclude columns: {}, value: {}", Condition.OR_LIKE_LEFT, condition.getIncludeColumns(), condition.getValue());
                    boolQuery.should(wildcardQuery(condition.getColumn(), "*" + condition.getValue()));
                });
        // 右模糊匹配
        orList.stream().filter(condition -> condition.getCondition().equals(Condition.OR_LIKE_RIGHT))
                .forEach(condition -> {
                    log.debug("【condition】{} exclude columns: {}, value: {}", Condition.OR_LIKE_RIGHT, condition.getIncludeColumns(), condition.getValue());
                    boolQuery.should(wildcardQuery(condition.getColumn(), condition.getValue() + "*"));
                });
        // 模糊匹配
        orList.stream().filter(condition -> condition.getCondition().equals(Condition.OR_LIKE))
                .forEach(condition -> {
                    log.debug("【condition】{} exclude columns: {}, value: {}", Condition.OR_LIKE, condition.getIncludeColumns(), condition.getValue());
                    boolQuery.should(wildcardQuery(condition.getColumn(), "*" + condition.getValue() + "*"));
                });
        boolQueryBuilder.must(boolQuery);
        return boolQueryBuilder;
    }

    /**
     * 构建查询条件
     */
    protected BoolQueryBuilder getBaseBoolQueryBuilder(List<Condition> conditions) {
        BoolQueryBuilder queryBuilder = boolQuery();
        conditions.forEach(condition -> {
            switch (condition.getCondition()) {
                case Condition.EQ:
                    // 等于
                    log.debug("【condition】{} column: {}, value: {}", Condition.EQ, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(termQuery(condition.getColumn(), condition.getValue()));
                    break;
                case Condition.NEQ:
                    // 不等于
                    log.debug("【condition】{} column: {}, value: {}", Condition.NEQ, condition.getColumn(), condition.getValue());
                    queryBuilder.mustNot(termQuery(condition.getColumn(), condition.getValue()));
                    break;
                case Condition.GT:
                    // 大于
                    log.debug("【condition】{} column: {}, value: {}", Condition.GT, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(rangeQuery(condition.getColumn()).gt(condition.getValue()));
                    break;
                case Condition.LT:
                    // 小于
                    log.debug("【condition】{} column: {}, value: {}", Condition.LT, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(rangeQuery(condition.getColumn()).lt(condition.getValue()));
                    break;
                case Condition.GTE:
                    // 大于等于
                    log.debug("【condition】{} column: {}, value: {}", Condition.GTE, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(rangeQuery(condition.getColumn()).gte(condition.getValue()));
                    break;
                case Condition.LTE:
                    // 小于等于
                    log.debug("【condition】{} column: {}, value: {}", Condition.LTE, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(rangeQuery(condition.getColumn()).lte(condition.getValue()));
                    break;
                case Condition.IN:
                    // 包含范围之内
                    log.debug("【condition】{} column: {}, value: {}", Condition.IN, condition.getColumn(), condition.getValues());
                    queryBuilder.filter(termsQuery(condition.getColumn(), condition.getValues()));
                    break;
                case Condition.IS_NULL:
                    // is null
                    log.debug("【condition】{} column: {}, value: {}", Condition.IS_NULL, condition.getColumn(), condition.getValues());
                    queryBuilder.mustNot(existsQuery(condition.getColumn()));
                    break;
                case Condition.IS_NOT_NULL:
                    // is not null
                    log.debug("【condition】{} column: {}, value: {}", Condition.IS_NOT_NULL, condition.getColumn(), condition.getValues());
                    queryBuilder.filter(existsQuery(condition.getColumn()));
                    break;
                case Condition.LIKE_LEFT:
                    // 左模糊
                    log.debug("【condition】{} column: {}, value: {}", Condition.LIKE_LEFT, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(wildcardQuery(condition.getColumn(), "*" + condition.getValue()));
                    break;
                case Condition.LIKE_RIGHT:
                    // 右模糊
                    log.debug("【condition】{} column: {}, value: {}", Condition.LIKE_RIGHT, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(wildcardQuery(condition.getColumn(), condition.getValue() + "*"));
                    break;
                case Condition.LIKE:
                    // 模糊
                    log.debug("【condition】{} column: {}, value: {}", Condition.LIKE, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(wildcardQuery(condition.getColumn(), "*" + condition.getValue() + "*"));
                    break;
                case Condition.LIKE_LEFT_ONE:
                    log.debug("【condition】{} column: {}, value: {}", Condition.LIKE_LEFT_ONE, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(wildcardQuery(condition.getColumn(), "?" + condition.getValue()));
                    break;
                case Condition.LIKE_RIGHT_ONE:
                    log.debug("【condition】{} column: {}, value: {}", Condition.LIKE_RIGHT_ONE, condition.getColumn(), condition.getValue());
                    queryBuilder.filter(wildcardQuery(condition.getColumn(), condition.getValue() + "?"));
                    break;
                default:
                    break;
            }
        });
        return queryBuilder;
    }

    /**
     * 获取集合
     *
     * @param wrapper       将要查询的条件封装到wrapper中
     * @param searchRequest 搜索请求
     * @return 数据集合
     */
    @SneakyThrows
    protected List<T> getSelectTs(ESQueryWrapper<T> wrapper, SearchRequest searchRequest, Integer size) {
        // 构建搜索资源
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 匹配所有
        if (wrapper == null) {
            searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        }
        // 按条件匹配
        else {
            searchSourceBuilder = getSearchSourceBuilder(wrapper.getConditions(), 1, size);
        }
        searchRequest.source(searchSourceBuilder);
        log.debug("【SelectTs】DSL: {}", searchSourceBuilder.toString());
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("【SelectTs】搜索异常：{}", e.getMessage());
            e.printStackTrace();
        } catch (ElasticsearchStatusException e) {
            if (e instanceof ElasticsearchException) {
                log.error("【SelectTs】", e.getCause());
                throw new RuntimeException("【selectMap】" + e.getCause());
            } else {
                e.printStackTrace();
                log.error("【SelectTs】索引库不存在 {}", e.getMessage());
                throw new RuntimeException("【selectMap】索引库 " + getIndexName() + " 不存在 ");
            }
        }
        assert searchResponse != null;
        SearchHit[] hits = searchResponse.getHits().getHits();
        JSONArray jsonArray = new JSONArray();
        for (SearchHit hit : hits) {
            jsonArray.add(JSON.parseObject(hit.getSourceAsString()));
        }
        return jsonArray.toJavaList(getEntityClass());
    }

    /**
     * 分页查询
     *
     * @param wrapper       将要查询的条件封装到wrapper中
     * @param pageNum       当前页
     * @param pageSize      每页容量
     * @param searchRequest 搜索请求
     * @return 分页实体类
     */
    protected Page<T> getPage(ESQueryWrapper<T> wrapper, Integer pageNum, Integer pageSize, SearchRequest searchRequest) {
        // 构建搜索资源
        SearchSourceBuilder sourceBuilder = getSearchSourceBuilder(wrapper.getConditions(), pageNum, pageSize);
        searchRequest.source(sourceBuilder);
        log.debug("DSL: {}", sourceBuilder.toString());
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("【page】分页搜索异常：{}", e.getMessage());
            e.printStackTrace();
        } catch (ElasticsearchStatusException e) {
            e.printStackTrace();
            log.error("【page】索引库不存在 {}", e.getMessage());
            throw new RuntimeException("索引库不存在" + getIndexName());
        }
        assert searchResponse != null;
        SearchHits responseHits = searchResponse.getHits();
        long total = responseHits.getTotalHits().value;
        SearchHit[] hits = responseHits.getHits();
        List<T> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            list.add(JSON.parseObject(hit.getSourceAsString(), getEntityClass()));
        }
        return Page.buildPage(list, pageNum, pageSize, total);
    }

    /**
     * 获取集合
     *
     * @param wrapper       将要查询的条件封装到wrapper中
     * @param searchRequest 搜索请求
     * @return map 集合
     */
    @SneakyThrows
    protected List<Map<String, Object>> getSelectMaps(ESQueryWrapper<T> wrapper, SearchRequest searchRequest, Integer size) {
        // 构建搜索资源
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 匹配所有
        if (wrapper == null) {
            searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        }
        // 按条件匹配
        else {
            searchSourceBuilder = getSearchSourceBuilder(wrapper.getConditions(), 1, size);
        }
        searchRequest.source(searchSourceBuilder);
        log.debug("【selectMaps】DSL: {}", searchSourceBuilder.toString());
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("【selectMaps】搜索异常：{}", e.getMessage());
            e.printStackTrace();
        } catch (ElasticsearchStatusException e) {
            e.printStackTrace();
            log.error("【selectMaps】索引库不存在 {}", e.getMessage());
            throw new RuntimeException("【selectMaps】索引库 {} 不存在 " + getIndexName());
        }
        assert searchResponse != null;
        SearchHit[] hits = searchResponse.getHits().getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            list.add(hit.getSourceAsMap());
        }
        return list;
    }

    /**
     * 分页查询
     *
     * @param wrapper       将要查询的条件封装到wrapper中
     * @param pageNum       当前页
     * @param pageSize      每页容量
     * @param searchRequest 搜索请求
     * @return 分页map
     */
    protected Page<Map<String, Object>> getPageMap(ESQueryWrapper<T> wrapper, Integer pageNum, Integer pageSize, SearchRequest searchRequest) {
        // 构建搜索资源
        SearchSourceBuilder sourceBuilder = getSearchSourceBuilder(wrapper.getConditions(), pageNum, pageSize);
        searchRequest.source(sourceBuilder);
        log.debug("【pageMap】DSL: {}", sourceBuilder.toString());
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("【pageMap】分页搜索异常：{}", e.getMessage());
            e.printStackTrace();
        } catch (ElasticsearchStatusException e) {
            e.printStackTrace();
            log.error("【pageMap】索引库不存在 {}", e.getMessage());
            throw new RuntimeException("【pageMap】索引库不存在" + getIndexName());
        }
        assert searchResponse != null;
        SearchHits responseHits = searchResponse.getHits();
        long total = responseHits.getTotalHits().value;
        SearchHit[] hits = responseHits.getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            list.add(hit.getSourceAsMap());
        }
        return Page.buildPage(list, pageNum, pageSize, total);
    }

    /**
     * 查询count的基础方法
     */
    protected Long getCount(CountRequest countRequest, ESQueryWrapper<T> wrapper) {
        log.debug("【count】CONDITION_LIST : {}", wrapper.conditions);
        BoolQueryBuilder boolQueryBuilder = getBaseBoolQueryBuilder(wrapper.conditions);
        countRequest.query(boolQueryBuilder);
        CountResponse countResponse = null;
        try {
            countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        assert countResponse != null;
        log.debug("【count】结果：{}", countResponse.getCount());
        return countResponse.getCount();
    }
}

